package server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.HashSet;
import java.util.Iterator;

import security.AsymmetricKeyEncryptor;
import security.SymmetricKeyEncryptor;

public class Server {

	public static final int PUBLIC_PRIVATE = 1 << 1;
	public static final int PASSWORD = 1 << 2;

	public static final int USER = 1 << 3;
	public static final int DATA = 1 << 4;

	private int mode;
	private Key privateKey;
	private String address;
	private int port;
	private HashSet<ClientHandler> clients;
	private MyServerSocket serverSocket;
        
        //Monitor
        private MonitorReadings readings;
        //Monitor

	public Server(int mode, int port) {
		this.mode = mode;
		this.port = port;
		clients = new HashSet<ClientHandler>();
		serverSocket = new MyServerSocket(address, port, this);
		try {
			privateKey = AsymmetricKeyEncryptor.getInstance().loadPrivateKey(
					"serverdata/server/");
			System.out.println("Server private key = " + privateKey);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public int getMode() {
		return mode;
	}

	public void setMode(int mode) {
		this.mode = mode;
	}

	public void initializeServer() {
		new Thread(serverSocket).start();
	}

	public synchronized void handleClientMessage(byte[] message,
			ClientHandler clientHandler) {
                //Monitor
                long timeBefore = System.currentTimeMillis();
                //Monitor
		// Decrypt message
		String decryptedMsg = "";
		switch (mode) {
		case PUBLIC_PRIVATE:
			decryptedMsg = new String(AsymmetricKeyEncryptor.getInstance()
					.decrypt(message, privateKey));
			break;
		case PASSWORD:
			decryptedMsg = new String(SymmetricKeyEncryptor.getInstance()
					.decrypt(message,
							clientHandler.getSymmetricKey()));
			break;
		}
                //Monitor
                long encryptionPeriod = System.currentTimeMillis() - timeBefore;
                if (isMonitorEnabled())
                   readings.addEncStats(encryptionPeriod, 1); 
                //Monitor
		System.out.println("Server received " + decryptedMsg + " from client " + clientHandler.toString());
		Iterator<ClientHandler> it = clients.iterator();
		ClientHandler temp;
		decryptedMsg = DATA + " " + decryptedMsg;
                //Monitor
                timeBefore = System.currentTimeMillis();
                //Monitor
		while (it.hasNext()) {
			temp = it.next();
			if (!temp.equals(clientHandler)) {
				sendEncrypted(decryptedMsg, temp);
			}
		}
                //Monitor
                long decryptionPeriod = System.currentTimeMillis() - timeBefore;
                if (isMonitorEnabled() && clients.size() > 1)
                   readings.addDecStats(decryptionPeriod, clients.size() - 1); 
                //Monitor
	}
	
	private void sendEncrypted(String msg, ClientHandler handler)
	{
		// TODO Encrypt message
		byte encryptedMsg[] = null;
		switch (mode) {
		case PUBLIC_PRIVATE:
			AsymmetricKeyEncryptor ake = AsymmetricKeyEncryptor
					.getInstance();
			try {
				PublicKey clientPublicKey = ake
						.loadPubliceKey("serverdata/" + handler.getUsername()
								+ "/");
				encryptedMsg = AsymmetricKeyEncryptor.getInstance().encrypt(msg.getBytes(),
								clientPublicKey);
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (InvalidKeySpecException e) {
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			break;
		case PASSWORD:
			encryptedMsg = SymmetricKeyEncryptor.getInstance().encrypt(msg.getBytes(),
							handler.getSymmetricKey());
			break;
		}
		handler.sendData(encryptedMsg.length+"");
		handler.sendData(encryptedMsg);
	}

	public void handleNewClient(OutputStream serverOut, InputStream serverIn) {
		// TODO Auto-generated method stub
		ClientHandler handler = new ClientHandler(serverOut, serverIn, this);
		boolean clientAuthenticated = handler.authenticate();
		if (clientAuthenticated) {
			// Send Client to all clients
			Iterator<ClientHandler> it = clients.iterator();
			ClientHandler temp;
			while (it.hasNext()) {
				temp = it.next();
				System.out.println("Sending Mssage " + USER + " " + handler.getUsername());
				sendEncrypted(USER + " " + handler.getUsername(), temp);
				sendEncrypted(USER + " " + temp.getUsername(), handler);
			}
			clients.add(handler);
			handler.startReceivingData();
		}
	}

    void enableMonitor(boolean b) {
        if (b)
            readings = new MonitorReadings();
        else
            readings = null;
    }
    
    public boolean isMonitorEnabled() {
        return readings != null;
    }
    
    public MonitorReadings getMonitorReadings(){
        return readings;
    }
}
